package com.sheng.project.utils;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.id.UUIDGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author liusheng
 * @date 2022/09/07
 */
@Slf4j
@Data
@Component
@NoArgsConstructor
@AllArgsConstructor
public class SnowFlakeUtil extends UUIDGenerator {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1480166465631L;

    /**
     * 每一部分占用的位数
     * <p>
     * SEQUENCE_BIT：序列号占用的位数
     * MACHINE_BIT：机器标识占用的位数
     * DATACENTER_BIT：数据中心占用的位数
     */
    private final static long SEQUENCE_BIT = 12;
    public final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
    private final static long MACHINE_BIT = 5;
    public final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    private final static long DATACENTER_BIT = 5;
    /**
     * 每一部分的最大值
     */
    public final static long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
    /**
     * 终端ID：0~31
     * 数据中心ID：0~31
     */
    private static final Snowflake SNOWFLAKE = IdUtil.createSnowflake(1, 1);

    private static final String KEY = "com:sheng:snowflake";
    private static UUIDGenerator uuidGenerator = null;
    /**
     * 数据中心
     */
    private long datacenterId;
    /**
     * 机器标识
     */
    private long machineId;
    /**
     * 序列号
     */
    private long sequence = 0L;
    private long lastStmp = -1L;

    public SnowFlakeUtil(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId 不能大于 MAX_DATACENTER_NUM，或小于 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId 不能大于 MAX_MACHINE_NUM，或小于 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    public SnowFlakeUtil(ApplicationContext applicationContext, RedisTemplate<String, String> redisTemplate) {
        Environment environment = applicationContext.getEnvironment();
        Boolean snowflake = environment.getProperty("application.sheng.snowflake.enable", Boolean.class, Boolean.FALSE);
        if (snowflake) {
            Long dataCenterId = environment.getProperty("application.sheng.snowflake.data-center-id", Long.class);
            Long workId = environment.getProperty("application.sheng.snowflake.work-id", Long.class);
            if (dataCenterId != null || workId != null) {
                String host = String.join(",", IpUtil.getAllIntet4Addresses());
                String port = environment.getProperty("server.port", String.class);
                String ip = host + ":" + port;
                Boolean added = redisTemplate.opsForZSet().add(KEY, ip, 1);
                if (Boolean.FALSE.equals(added)) {
                    log.warn("IP 重复");
                }
                List<String> list = new ArrayList<>(Objects.requireNonNull(redisTemplate.opsForZSet().range(KEY, 0, MAX_DATACENTER_NUM * MAX_MACHINE_NUM - 1)));
                int index = list.indexOf(ip);
                dataCenterId = index / MAX_MACHINE_NUM;
                workId = index / MAX_MACHINE_NUM;
            }
            uuidGenerator = new SnowFlakeUtil(dataCenterId, workId);
        }
        log.info("加载 ID 生成：{}", uuidGenerator);
    }

    public static String hutoolNextId() {
        return SNOWFLAKE.nextIdStr();
    }

    public synchronized long nextId() {
        long currStmp = System.currentTimeMillis();
        if (currStmp < lastStmp) {
            throw new RuntimeException("时间戳后移，拒绝生成 UUID");
        }
        if (currStmp == lastStmp) {
            // 相同毫秒内序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 统一毫秒内序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            // 不同毫秒内，序列号置为 0
            sequence = 0L;
        }
        lastStmp = currStmp;
        return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分
                | datacenterId << DATACENTER_LEFT       // 数据中心部分
                | machineId << MACHINE_LEFT             // 机器标识部分
                | sequence;                             // 序列号部分
    }

    public long getNextMill() {
        long mill = System.currentTimeMillis();
        while (mill <= lastStmp) {
            mill = System.currentTimeMillis();
        }
        return mill;
    }
}
